package immutablecollections;

import immutablecollections.functions.*;
import java.io.*;
import java.util.*;

/**
 * The superclass of all `ImCollection` classes.
 * 
 * ## Introduction
 * 
 * <img src=" doc-files/classes-focus-on-collection.png" alt="classes" style="width: 40em"/></p>
 * 
 * This class is a subclass of {@link Collection} which
 * implements the standard Java interfaces {@link Collection} and {@link Iterable}.
 * 
 * 
 * All ImCollection subclasses "do the right thing" for:
 * 
 * {@link #size()},
 * {@link #isEmpty()},
 * {@link #contains(Object)},
 * {@link #containsAll(Collection)},
 * {@link #iterator()},
 * {@link #toArray()}
 * 
 * However, the mutator methods:
 * 
 * {@link #add},
 * {@link #remove(Object)},
 * {@link #addAll(Collection)}, 
 * {@link #removeAll(Collection)},
 * {@link #retainAll(Collection)},
 * {@link #clear()}
 *  
 * all throw {@link UnsupportedOperationException}.
 * 
 * The methods
 * {@link #containsAll(Collection)},
 * {@link #iterator()}
 * and the two {@link #toArray()} methods
 * are implemented in this class by using
 * {@link #iterator()} and {@link #contains(Object)}
 * 
 * What this means in practice is that you can safely supply an `ImCollection` to any method that expects a `Collection`
 * as long as that method doesn't try to modify the collection.
 * 
 * All `ImCollections` prohibit `null` elements.
 * 
 */
public abstract class ImCollection<T> implements Collection<T>, Serializable
{
    /**
     * @deprecated Unsupported operation since ImCollections are immutable.
     */
    public boolean remove(Object o)
    {
        throw new UnsupportedOperationException();
    }

    /**
     * @deprecated Unsupported operation since ImCollections are immutable.
     */
    public boolean add(T e)
    {
        throw new UnsupportedOperationException();
    }

    /**
     * @deprecated Unsupported operation since ImCollections are immutable.
     */
    public boolean addAll(Collection<? extends T> c)
    {
        throw new UnsupportedOperationException();
    }

    /**
     * @deprecated Unsupported operation since ImCollections are immutable.
     */
    public boolean removeAll(Collection<?> c)
    {
        throw new UnsupportedOperationException();
    }

    /**
     * @deprecated Unsupported operation since ImCollections are immutable.
     */
    public boolean retainAll(Collection<?> c)
    {
        throw new UnsupportedOperationException();
    }

    /**
     * @deprecated Unsupported operation since ImCollections are immutable.
     */
    public void clear()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * The number of elements in `this`.
     */
    @Override
    public abstract int size();

    /**
     * `true` if `this` is empty.
     */
    @Override
    public boolean isEmpty()
    {
        return size() == 0;
    }

    @Override
    public abstract boolean contains(Object o);

    /**
     * Returns an iterator over the elements in this collection.
     *
     * Note that the iterator returned by this method will throw an
     * `UnsupportedOperationException` in response to its
     * `remove` method.
     *
     */
    @Override
    public abstract Iterator<T> iterator();

    @Override
    public boolean containsAll(Collection<?> c)
    {
        for (Object i : c)
        {
            if (!contains(i))
                return false;
        }
        return true;
    }

    @Override
    public Object[] toArray()
    {
        Object[] r = new Object[size()];

        int i = 0;
        for (Object thing : this)
        {
            r[i++] = thing;
        }

        return r;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <U> U[] toArray(U[] a)
    {
        int size = size();
        U[] r = a.length >= size
                ? a
                : (U[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);

        int i = 0;
        for (T thing : this)
        {
            r[i++] = (U) thing;
        }

        return r;
    }

    /**
     * 
     * Combine the elements of `this` using the function `fn`, starting with `start`.
     * 
     * `fold` is what functional languages often call `foldl`.    
     *    
     * `fold` applies the two-argument function `fn` to `start` and the first element of `this` to get a value `v1`.
     *  
     *     v1 = fn(start, list.at(1))
     *      
     * It then applies `fn` to `v1` and the second element to get a value v2.
     *  
     *     v2 = fn(v1, list.at(2))
     *  
     * It carries on in this fashion until it reaches the end:
     *  
     *     v1 = fn(start, list.at(1))
     *     v2 = fn(v1, list.at(2))
     *     v3 = fn(v2, list.at(3))
     *     ...
     *     vn = fn(vn-1, list.at(n))
     *  
     * The result is the last value obtained.
     *  
     * Another way of visualising this is to imagine that the two argument function `fn` is the `*` operator. Then
     * the invocation of `fn` on arguments `a` and `b` can be written:
     *  
     *      a * b
     *  
     * With this convention, we have (using `ImList` as an example):
     *  
     *      ImList.on(a, b, c).fold(start, fn)   =>  ((start * a) * b) * c
     *  
     * ### The 'nth element'
     *  
     * In the above, the *nth element* of `this` has an obvious meaning for `ImList` and `ImShelf`. For
     * `ImSet` and `ImSortedSet` it is the same as:
     *  
     *     this.toArray().[n-1]
     *       
     * ### Examples
     *  
     *     ImList.empty().fold(start, any-function)   =>   start
     * 
     *     Function2<Integer> maxFn = FnFactory.on(Math.class).getFnStatic(int.class, "max", int.class, int.class);
     *      
     *     ImList.onArray(1, 2, 3).fold(0, maxFn)  =>  3
     *      
     * If we have an `add()` method:
     *       
     *     public static int add(int a, int b)
     *     {
     *         return a + b;
     *     }
     *  
     * then we can easily sum the elements in a list:
     *      
     *     Function2<Integer> addFn = FnFactory.on(blah).getFnStatic(int.class, "add", int.class, int.class);
     *      
     *     ImList.onArray(1, 2, 3).fold(0, addFn)  =>  6
     *  
     * See the <a href="{@docRoot}/immutablecollections/functions/package-summary.html">package summary</a>
     * for more details of how functions work.
     * 
     */
    public <O> O fold(O start, Function2<O> fn)
    {
        O acc = start;

        for (Object e : this)
        {
            acc = fn.invoke(acc).invoke(e);
        }

        return acc;
    }

}
